package com.example.demo.component;

import com.alibaba.fastjson.JSON;
import com.example.demo.config.AppConfig;
import com.example.demo.model.Message;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.annotation.MergedAnnotationPredicates;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 通过注解获取 component 中的 ServerEndpoint
 * ()中的 url 为前端连接 socket 服务器的地址
 */

//  可以使用/{}的方式，在 url 中传参
//@ServerEndpoint("/chat/{topic}")
@ServerEndpoint(value = "/chat",configurator = GetHttpSession.class)//  也可以不传参数，在 Session 中获取参数
@Component
@Slf4j
public class WebSocketServer {
    /**
     * 使用 concurrentHashMap 来存放每个客户端对应的 webSocketServerMap 对象
     */
    private static final Map<String,WebSocketServer> webSocketServerMap = new ConcurrentHashMap();

    /**
     * 静态变量，用来记录当前在线连接数。应该把它设计成线程安全的。
     */
    private static volatile int onlineCount = 0;

    /**
     * 存放与客户端的回话，通过它可以给客户端发送数据
     * <注意：此 session 与 HTTPServletSession 不一样>
     */
    private Session session;

    /**
     * 接受频道，用与识别发送给客户端的 session，一般是用户名（唯一）
     */
    //  private String topic = "";
    private HttpSession httpSession;    //使用 Session 识别，更安全

    /**
     * 连接成功后自动调用该方法
     * @param session 建立 webSocket 连接的session
     * @param topic 接受的频道
     */
    /*@OnOpen
    public void onOpen(Session session, @PathParam("topic") String topic) {
        //  先给连接的对象赋值
        this.session = session;
        this.topic = topic;

        //  把此次的连接存入map
        if(!webSocketServerMap.containsKey(topic)) {
            webSocketServerMap.put(topic,this);
            addOnlineCount();
        }
        
        //  发送信息给客户端
        this.sendMessage("连接成功",this.session);
        log.info("用户" + topic + " 已上线.");
    }*/

    /**
     * 连接成功后执行该方法
     * @param session 与客户端连接回话
     * @param endpointConfig 用于获取 HttpSession
     */
    @OnOpen
    public void onOpen(Session session,EndpointConfig endpointConfig) {
        //  先给连接的对象赋值
        this.session = session;
        HttpSession httpsession = (HttpSession) endpointConfig.getUserProperties().get(AppConfig.WEBSOCKET_SESSION_KEY);
        this.httpSession = httpsession;

        //  把此次的连接存入map
        //  获取 username
        String username = (String) httpsession.getAttribute(AppConfig.HTTP_SESSION_KEY);

        if(!webSocketServerMap.containsKey(username)) {
            webSocketServerMap.put(username,this);
            addOnlineCount();
        }
        //  发送信息给客户端
        this.sendMessage("连接成功",this.session);
        log.info("用户" + username + " 已上线.");
    }


    /**
     * 关闭连接自动调用该方法
     */
    /*@OnClose
    public void onClose(@PathParam("topic") String topic) {
        if(webSocketServerMap.containsKey(topic)) {
            webSocketServerMap.remove(topic);
            subOnlineCount();
        }
    }*/

    @OnClose
    public void onClose() {
        //  获取用户名
        String username = (String) httpSession.getAttribute(AppConfig.HTTP_SESSION_KEY);
        if(webSocketServerMap.containsKey(username)) {
            webSocketServerMap.remove(username);
            subOnlineCount();
        }
    }

    /**
     * 服务器 收到客户端后调用该方法
     * @param message 客户端发送过来的信息
     */
    @OnMessage
    public void OnMessage(String message) {
        //  <发送信息时，前后端要约定信息格式>
        //  消息可以保存到数据库、redis
        if(StringUtils.isNotEmpty(message)) {
            //  信息不为空，解析为 java 对象
            Message msg = JSON.parseObject(message,Message.class);
            if(StringUtils.isEmpty(msg.getTo())) {
                log.warn(msg.getTo() + " 用户不在线.");
                return;
            }

            //  获取接受信息的 客户端的回话
            String to = msg.getTo();
            Session sessionTo = webSocketServerMap.get(to).session;
            //  发送
            sendMessage(msg.getMsg(),sessionTo);
            log.info(msg.getFrom() + "发送：" + msg.getMsg() + " to " + msg.getTo());
        }
    }

    /**
     * 连接出错时调用该方法
     * @param session 会话
     * @param error 错误
     */
    @OnError
    public void onError(Session session,Throwable error) {
        log.warn("发生错误");
        error.printStackTrace();
    }



    /**
     * 发送信息给指定客户端
     * @param message 信息
     * @param sessionTo 客户端 session
     */
    private void sendMessage(String message,Session sessionTo) {
        try {
            sessionTo.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketServer.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketServer.onlineCount--;
    }
}
